package com.ziytek.web.citizen.service.impl;

import com.ziytek.web.citizen.config.ConfigInit;
import com.ziytek.web.citizen.constant.ServerConstant;
import com.ziytek.web.citizen.handler.ServiceChannelInitializer;
import com.ziytek.web.citizen.handler.client.ClientTcpChannelInitializer;
import com.ziytek.web.citizen.service.itf.BootNettyTcpServer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;

/**
 * TCP服务端启动类
 *
 * @author fenghx
 * @version v1.0
 * @className BootNettyTcpServerImpl
 * @date 2023/8/7 15:09
 **/
@Slf4j
@Component(ServerConstant.TCP_SERVICE_BEAN)
@Lazy(value = false)
public class BootNettyTcpServerImpl implements BootNettyTcpServer {

    @Async("myAsyncExecutor")
    @Override
    public void bind(ConfigInit configInit) throws Exception {

        log.info("netty server start ip[{}] port[{}]", configInit.getConfig().getTcpServerIp(), configInit.getConfig().getTcpServerPort());

        /**
         * 	配置服务端的NIO线程组
         * 	NioEventLoopGroup 是用来处理I/O操作的Reactor线程组
         * 	bossGroup：用来接收进来的连接，workerGroup：用来处理已经被接收的连接,进行socketChannel的网络读写，
         * 	bossGroup接收到连接后就会把连接信息注册到workerGroup
         * 	workerGroup的EventLoopGroup默认的线程数是CPU核数的二倍
         */
        var bossGroup = new NioEventLoopGroup();
        var workerGroup = new NioEventLoopGroup();

        try {
            /**
             * ServerBootstrap 是一个启动NIO服务的辅助启动类
             */
            var serverBootstrap = new ServerBootstrap();
            /**
             * 	设置group，将bossGroup， workerGroup线程组传递到ServerBootstrap
             */
            serverBootstrap = serverBootstrap.group(bossGroup, workerGroup);

            /**
             * ServerSocketChannel是以NIO的selector为基础进行实现的，用来接收新的连接，这里告诉Channel通过NioServerSocketChannel获取新的连接
             */
            serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);

            //  option是设置 bossGroup，childOption是设置workerGroup

            /**
             * 服务端接受连接的队列长度，如果队列已满，客户端连接将被拒绝(队列被接收后，拒绝的客户端下次连接上来只要队列有空余就能连上)
             */
            //serverBootstrap = serverBootstrap.option(ChannelOption.SO_BACKLOG, 128);
            serverBootstrap = serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            //注意:这行影响的底层接收缓冲区（即滑动窗口）大小，仅决定了 netty 读取的最小单位，netty 实际每次读取的一般是它的整数倍
            //serverBootstrap = serverBootstrap.option(ChannelOption.SO_RCVBUF, 1024 * 10);

//            serverBootstrap = serverBootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT);
//            serverBootstrap = serverBootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(DEFAULT_MAX_SIZE));
//            serverBootstrap = serverBootstrap.option(ChannelOption.RCVBUF_ALLOCATOR,
//                    new AdaptiveRecvByteBufAllocator(DEFAULT_MIN_SIZE, DEFAULT_INITIAL_SIZE, DEFAULT_MAX_SIZE));

            /**
             * 立即发送数据，默认值为Ture（Netty默认为True而操作系统默认为False）。
             * 该值设置Nagle算法的启用，改算法将小的碎片数据连接成更大的报文来最小化所发送的报文的数量，如果需要发送一些较小的报文，则需要禁用该算法。
             * Netty默认禁用该算法，从而最小化报文传输延时。
             */
            serverBootstrap = serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
            // 调整 netty 的接收缓冲区（byteBuf）
            //serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(64, 2048, 65536));

            /**
             * 连接保活，默认值为False。启用该功能时，TCP会主动探测空闲连接的有效性。
             * 可以将此功能视为TCP的心跳机制，默认的心跳间隔是7200s即2小时, Netty默认关闭该功能。
             */
            serverBootstrap = serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);

            /**
             * 设置 I/O处理类,主要用于网络I/O事件，记录日志，编码、解码消息
             */
            serverBootstrap = serverBootstrap.childHandler(new ServiceChannelInitializer<SocketChannel>());

            /**
             * 绑定端口，同步等待成功
             */
            //ChannelFuture f = serverBootstrap.bind(port).sync();
            var f = serverBootstrap.bind(new InetSocketAddress(configInit.getConfig().getTcpServerIp(), configInit.getConfig().getTcpServerPort())).sync();
            if (f.isSuccess()) {
                log.info(" netty server started success and listened on {}", f.channel().localAddress());
                //7、监听通道关闭事件，应用程序会一直等待，直到channel关闭

                /**
                 * 等待服务器监听端口关闭
                 */
                if (f.channel() != null) {
                    f.channel().closeFuture().sync();
                }
            }
        } catch (InterruptedException e) {
            log.error("netty server start error:", e);
        } finally {
            /**
             * 退出，释放线程池资源
             */
            bossGroup.shutdownGracefully().sync();
            workerGroup.shutdownGracefully().sync();
        }
    }

    @Async("myAsyncExecutor")
    @Override
    public void bind(String ip, Integer port) throws Exception {
        log.info("server start ip[{}] port[{}]", ip, port);

        var bossGroup = new NioEventLoopGroup();
        var workerGroup = new NioEventLoopGroup();

        try {

            var serverBootstrap = new ServerBootstrap();
            serverBootstrap = serverBootstrap.group(bossGroup, workerGroup);
            serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap = serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            serverBootstrap = serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
            serverBootstrap = serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);

            serverBootstrap = serverBootstrap.childHandler(new ClientTcpChannelInitializer<SocketChannel>());

            port = port == null ? 65505 : port;
            var f = serverBootstrap.bind(new InetSocketAddress(ip, port)).sync();
            if (f.isSuccess()) {
                log.info("web TCP server started success and listened on {}", f.channel().localAddress());
                if (f.channel() != null) {
                    f.channel().closeFuture().sync();
                }
            }
        } catch (InterruptedException e) {
            log.error("netty server start error:", e);
        } finally {
            bossGroup.shutdownGracefully().sync();
            workerGroup.shutdownGracefully().sync();
        }
    }

}
